from typing import Tuple, List, Dict, Set, Any
import re
import json
from typing import Tuple, List, Dict, Set, Any, Union, Optional
from src.tools.dms_operator import dmsoperator
from src.tools.chain import gen_chain
from src.find_kv_and_check_word.prompt import Prompt
from src.tools.information_exchange import pub_gen
import logging
from config import MARKDWON_LEN_IN_FIND_KV_AND_CHECK_WORD

# 设置日志配置
logging.basicConfig(level=logging.INFO)

##################### required #####################
def get_full_text_and_positions(data: dict) -> Tuple[str, List[Dict]]:
    """
    从文档数据中提取完整的文本和每个文本段的位置。

    参数：
    - data (dict): 文档数据，包含段落信息和表格信息。

    返回值：
    - full_text (str): 文档的完整文本内容，将所有文本段拼接而成。
    - segment_positions (list of dict): 文本段的位置列表。每个元素是一个字典，包含：
        - 'segment' (dict): 文本段的信息，包括索引、内容等。
        - 'start' (int): 该文本段在全文本中的起始位置索引。
        - 'end' (int): 该文本段在全文本中的结束位置索引。
    """

    segments = []

    # 处理paragraphInfos，提取正文段落的文本段信息
    for para in data.get('paragraphInfos', []):
        segment = {
            'index': para.get('index'),  # 段落索引
            'sectionIndex': para.get('sectionIndex'),  # 节索引
            'bodyChildIndex': para.get('bodyChildIndex'),  # 正文子节点索引
            'operationType': 0,  # 操作类型，默认为0
            'documentObjectType': 0,  # 文档对象类型，0表示正文
            'content': para.get('text', ''),  # 文本内容
            'fullIndex': para.get('index')  # 用于比较位置的完整索引
        }
        segments.append(segment)

    # 处理tableInfos，提取表格中的文本段信息
    def extract_table_texts(table_infos):
        """
        递归提取表格中的文本段信息。

        参数：
        - table_infos (list of dict): 表格信息列表
        """
        for table in table_infos:
            for cell in table.get('tableCellInfos', []):
                for para in cell.get('paragraphInfos', []):
                    segment = {
                        'index': table.get('index'),  # 表格索引
                        'sectionIndex': table.get('sectionIndex'),  # 表格节索引
                        'bodyChildIndex': table.get('bodyChildIndex'),  # 表格正文子节点索引
                        'rowIndex': cell.get('rowIndex'),  # 单元格行索引
                        'columnIndex': cell.get('columnIndex'),  # 单元格列索引
                        'para_index': para.get('index'),  # 段落索引
                        'para_sectionIndex': para.get('sectionIndex'),  # 节索引
                        'para_bodyChildIndex': para.get('bodyChildIndex'),  # 正文子节点索引
                        'operationType': 0,  # 操作类型，默认为0
                        'documentObjectType': 2,  # 文档对象类型，2表示表格
                        'content': para.get('text', ''),  # 文本内容
                        'fullIndex': table.get('index')  # 用于比较位置的完整索引（使用表格索引）
                    }
                    segments.append(segment)
                    # 如果段落中有嵌套的表格，递归处理
                    if para.get('tableInfos'):
                        extract_table_texts(para['tableInfos'])
                # 如果单元格中有嵌套的表格，递归处理
                if cell.get('tableInfos'):
                    extract_table_texts(cell['tableInfos'])
            # 如果表格中有嵌套的表格，递归处理
            if table.get('tableInfos'):
                extract_table_texts(table['tableInfos'])

    # 提取所有表格中的文本段信息
    extract_table_texts(data.get('tableInfos', []))

    # 重建全文本，并记录每个文本段在全文本中的起始和结束位置
    full_text = ''
    segment_positions = []

    for segment in segments:
        content = segment['content']  # 文本段内容
        start_pos = len(full_text)  # 当前文本段在全文本中的起始位置
        full_text += content  # 将文本段内容添加到全文本
        end_pos = len(full_text) - 1  # 当前文本段在全文本中的结束位置
        segment_positions.append({
            'segment': segment,  # 文本段的信息
            'start': start_pos,  # 起始位置
            'end': end_pos  # 结束位置
        })

    return full_text, segment_positions

def find_all_occurrences_in_full_text(full_text: str, segment_positions: List[Dict], keywords: Set[str]) -> Dict[str, List[Tuple[int, List[Dict]]]]:

    """
    在全文本中查找所有指定关键词的出现位置，并返回详细的匹配信息。

    参数：
    - full_text (str): 文档的完整文本内容。
    - segment_positions (list of dict): 文本段的位置列表，由 get_full_text_and_positions 函数返回。
    - keywords (set of str): 要查找的关键词集合。

    返回值：
    - keyword_occurrences (dict): 关键词匹配结果的字典。键是关键词，值是一个列表，包含多个元组，每个元组包含：
        - 匹配位置索引 (int)
        - 匹配的文本段列表 (list of dict)，每个文本段包含匹配的部分信息
    """

    # 初始化关键词匹配结果的字典
    keyword_occurrences = {k: [] for k in keywords}

    for keyword in keywords:
        index = full_text.find(keyword)  # 在全文本中查找关键词的起始位置
        while index != -1:
            keyword_start = index  # 关键词在全文本中的起始位置
            keyword_end = index + len(keyword) - 1  # 关键词在全文本中的结束位置

            # 初始化匹配的部分列表
            matched_parts = []
            part_index = 1  # 部分索引，用于标识关键词被拆分成的第几部分

            # 遍历所有文本段，找到与关键词匹配部分重叠的段落
            for segment_pos in segment_positions:
                segment_start = segment_pos['start']  # 文本段在全文本中的起始位置
                segment_end = segment_pos['end']  # 文本段在全文本中的结束位置
                if segment_end < keyword_start:
                    continue  # 当前文本段在关键词之前，继续下一个文本段
                if segment_start > keyword_end:
                    break  # 当前文本段在关键词之后，停止遍历
                # 计算关键词和文本段的重叠部分
                overlap_start = max(segment_start, keyword_start)
                overlap_end = min(segment_end, keyword_end)
                # 计算重叠部分在文本段内的相对起始和结束位置
                segment_relative_start = overlap_start - segment_start
                segment_relative_end = overlap_end - segment_start
                content = segment_pos['segment']['content']  # 文本段内容
                # 提取匹配的文本
                matched_text = content[segment_relative_start:segment_relative_end + 1]
                # 创建匹配部分的信息
                matched_segment = segment_pos['segment'].copy()
                matched_segment['matchedText'] = matched_text  # 实际匹配到的文本
                matched_segment['keyword'] = keyword  # 关键词
                matched_segment['position'] = index  # 关键词在全文本中的起始位置
                matched_segment['partIndex'] = part_index  # 部分索引
                part_index += 1
                matched_parts.append(matched_segment)
            # 将匹配结果添加到关键词匹配结果的字典中
            keyword_occurrences[keyword].append((index, matched_parts))
            # 查找下一个匹配位置
            index = full_text.find(keyword, index + 1)

    # 按照关键词在全文本中的出现顺序排序
    for k in keyword_occurrences:
        keyword_occurrences[k].sort(key=lambda x: x[0])

    return keyword_occurrences

def process_keywords_and_values(data: dict, kv_json: dict) -> List[Dict]:
    """
    处理关键词和值列表，找到它们在文档中的位置信息，并以指定的JSON格式输出。

    参数：
    - data (dict): 文档数据，包含段落信息和表格信息。
    - kv_json (dict): 包含关键词和值的JSON数据。

    返回值：
    - output (list of dict): 包含关键词和值位置信息的列表。
    """

    kv_list = kv_json['result']

    # 提取所有关键词和值
    keywords = [item['keyword'] for item in kv_list]  # 关键词列表
    values = [item['value'] for item in kv_list]  # 值列表

    # 获取全文本和文本段位置
    full_text, segment_positions = get_full_text_and_positions(data)

    # 查找所有关键词和值的出现位置
    keyword_occurrences = find_all_occurrences_in_full_text(full_text, segment_positions, set(keywords))
    value_occurrences = find_all_occurrences_in_full_text(full_text, segment_positions, set(values))

    output = []

    # 用于记录每个关键词在文档中已使用的匹配项索引
    keyword_used_indices = {}  # {keyword: used_index}
    value_used_indices = {}  # {value: used_index}

    # 用于记录每个关键词在文档中已使用的 fullIndex，确保 index 递增
    keyword_last_fullIndex = {}  # {keyword: last_fullIndex}
    value_last_fullIndex = {}  # {value: last_fullIndex}

    for item in kv_list:
        kv_index = item.get('index')  # 关键词和值对的索引
        keyword = item.get('keyword')  # 关键词
        value = item.get('value')  # 值
        attribute = item.get('attribute')  # 属性

        # 初始化用于关键词和值的匹配项索引
        if keyword not in keyword_used_indices:
            keyword_used_indices[keyword] = 0
            keyword_last_fullIndex[keyword] = -1  # 初始为 -1，表示还未匹配
        if value not in value_used_indices:
            value_used_indices[value] = 0
            value_last_fullIndex[value] = -1

        kw_occurrences_for_keyword = keyword_occurrences.get(keyword, [])  # 当前关键词的所有匹配项
        val_occurrences_for_value = value_occurrences.get(value, [])  # 当前值的所有匹配项

        # 找到下一个符合条件的关键词匹配项
        kw_matched = False
        while keyword_used_indices[keyword] < len(kw_occurrences_for_keyword):
            kw_position, kw_parts = kw_occurrences_for_keyword[keyword_used_indices[keyword]]
            kw_fullIndex = min([part.get('fullIndex', 0) for part in kw_parts])  # 获取关键词匹配部分的最小 fullIndex

            # 检查 fullIndex 是否递增
            if kw_fullIndex > keyword_last_fullIndex[keyword]:
                kw_matched = True
                keyword_last_fullIndex[keyword] = kw_fullIndex  # 更新关键词的 last_fullIndex
                keyword_used_indices[keyword] += 1
                break
            else:
                keyword_used_indices[keyword] += 1  # 跳过已使用的或不符合条件的匹配项

        # 同理，找到下一个符合条件的值匹配项
        val_matched = False
        while value_used_indices[value] < len(val_occurrences_for_value):
            val_position, val_parts = val_occurrences_for_value[value_used_indices[value]]
            val_fullIndex = min([part.get('fullIndex', 0) for part in val_parts])  # 获取值匹配部分的最小 fullIndex

            # 检查 fullIndex 是否递增，且值的位置不在关键词之前
            if val_fullIndex > value_last_fullIndex[value] and val_fullIndex >= kw_fullIndex:
                val_matched = True
                value_last_fullIndex[value] = val_fullIndex  # 更新值的 last_fullIndex
                value_used_indices[value] += 1
                break
            else:
                value_used_indices[value] += 1  # 跳过已使用的或不符合条件的匹配项
        
        if not kw_matched and not val_matched:
            # 没有找到符合条件的关键词和值的匹配项，跳过该项
            print(f"未匹配到kv_index [{kv_index}]")
            continue

        # 为关键词的每个匹配部分添加 partIndex
        for idx, part in enumerate(kw_parts, 1):
            part['partIndex'] = idx
        # 为值的每个匹配部分添加 partIndex
        for idx, part in enumerate(val_parts, 1):
            part['partIndex'] = idx

        output_item = {
            'kv_index': kv_index,  # 关键词和值对的索引
            'attribute': attribute,  # 关键词的属性
            'keyword': kw_parts,  # 关键词的匹配部分列表
            'value': val_parts  # 值的匹配部分列表
        }
        output.append(output_item)

    return output

def clean_json_string(json_string: str) -> str:
    """
    从输入字符串中提取位于 ```json 和 ``` 之间的内容。

    参数:
        s (str): 包含 ```json{内容}``` 的字符串，可能有其他内容。

    返回:
        str: 提取的 {内容}，如果未找到则返回 None。
    """
    # 定义正则表达式模式，匹配 ```json 开始和 ``` 结束之间的内容
    pattern = r'```json(.*?)```'
    
    # 搜索匹配的内容，使用 re.DOTALL 以匹配包括换行符在内的所有字符
    match = re.search(pattern, json_string, re.DOTALL)
    
    if match:
        # 提取并返回匹配组，去除可能的空白符
        content = match.group(1).strip()
        return content
    else:
        # 未找到匹配内容，返回 None 或者根据需要修改
        return None

def grouping_based_on_different_kvs(json1: Dict[str, Any], json2: Dict[str, Any]) -> Dict[str, Any]:
    """
    在不同关键词组中，再基于不同值进行分组。

    参数：
    - json1 (Dict[str, Any]): 模型找到的 "keyword value pair" 信息。
    - json2 (Dict[str, Any]): 模型根据关键字的 "keyword" 和 "object" 信息分组后的 JSON。

    返回值：
    - Dict[str, Any]: 根据 "keyword value pair" 中的 "value" 进行更细致分类的 JSON。

    功能描述：
    - 基于 json1 中的 index 到 value 的映射，对 json2 中的 indices 进行分组，
      按照相同的 value 将 indices 分组，形成更细致的分类。
    """

    # 创建 index 到 value 的映射
    index_to_value: Dict[int, Any] = {
        entry["index"]: entry["value"]
        for entry in json1.get("result", [])
    }

    output_result: List[Dict[str, Any]] = []
    value_based_index: int = 1

    # 遍历 json2 中的每个分组
    for group in json2.get("result", []):
        indices: List[int] = group.get("indices", [])
        value_to_indices: Dict[Any, List[int]] = {}

        # 根据 value 对 indices 进行分组
        for idx in indices:
            value = index_to_value.get(idx)
            if value is not None:
                value_to_indices.setdefault(value, []).append(idx)
            else:
                # 处理未找到对应 value 的情况
                logging.info("找到 keyword 的 value 。")
                continue

        # 构建 value_based_group 列表
        value_based_group: List[Dict[str, Any]] = [
            {"indices": idx_list} for idx_list in value_to_indices.values()
        ]

        # 将分组数据添加到输出结果中
        output_result.append({
            "value_based_index": value_based_index,
            "value_based_group": value_based_group
        })
        value_based_index += 1

    return {"result": output_result}

def get_different_kvs_based_group(file_id: str, more_keywords: Optional[List[str]], single_file_flag: bool, task_id: str, file_info: Dict[str, Any]) -> Dict[str, Any]:
    """
    返回三个JSON字符串：
    1. 大模型第一次的输出（找到的关键词和值）。
    2. 包含关键词和值的位置信息的JSON。
    3. 基于不同关键词和不同值的分组信息。

    参数：
    - file_id (str): 文档在MES中的file_id。
    - more_keywords (Optional[List[str]]): 额外需要检查的关键词列表。
    - task_id (str): 任务ID。
    - file_info (Dict[str, Any]): 文件信息字典。

    返回值：
    - Dict[str, Any]: 包含code、info和data的字典。
    """
    # 一、生成文档的Markdown内容
    try:
        docx_2_markdown: str = dmsoperator.get_fileconversion(file_id, 4)
        logging.info("生成Markdown内容成功。")
    except Exception as e:
        return {
            'code': 500,
            'info': f"生成Markdown内容出错: {str(e)}",
            'data': {}
        }
    # 1、判断Markdown内容长度，是否大于markdown_len个字符
    markdown_len = MARKDWON_LEN_IN_FIND_KV_AND_CHECK_WORD
    # 如果大于，则进行拆分处理
    if len(docx_2_markdown) > markdown_len:
        print("markdown长度为：", len(docx_2_markdown))
        # 创建一个空列表，以存储处理后合并的md_result.json
        merged_list = []

        # 将markdown以不大于markdown_len的长度进行拆分
        md_secs: list = [docx_2_markdown[i:i + markdown_len] for i in range(0, len(docx_2_markdown), markdown_len)]

        for i, md_sec in enumerate(md_secs):
            # 合并Markdown内容和提示词
            prompt = Prompt()
            prompt_with_content: str = prompt.single_doc_find_keyword_prompt(
                f"'''{md_sec}'''", more_keywords
            )
            logging.info(f"第{i+1}个markdown分片的提示词合成成功！")
            # 传入大模型生成结果（大模型找关键词）
            model_1st_output: str = gen_chain(prompt_with_content)
            model_1st_output = model_1st_output.replace(r"\~", "~")
            # 提取JSON内容
            model_1st_output_clean: str = clean_json_string(model_1st_output)
            # 处理大模型的输出
            # 如果输出为空
            if not model_1st_output_clean:
                continue
            # 如果输出为"null"
            elif model_1st_output_clean == "null":
                continue
            # 如果有输出
            else:
                # 解析JSON
                model_1st_output_clean_dict: Dict[str, Any] = json.loads(model_1st_output_clean)
                # 扩展JSON键名
                model_1st_output_clean_dict = model_1st_output_expand_keys(model_1st_output_clean_dict)
                # 提取大模型第一次输出的结果部分
                model_1st_output_clean_dict_result: List[Dict[str, Any]] = model_1st_output_clean_dict['result']

                # 合并列表并更新index字段
                for item in model_1st_output_clean_dict_result:
                    item['index'] = len(merged_list) + 1  # 根据已合并列表长度更新index
                    merged_list.append(item)
        
        model_1st_output_clean_dict = {
            'result': merged_list
        }
        
    #如果不大于，则不进行拆分处理
    else:   
        # 二、合并Markdown内容和提示词
        prompt = Prompt()
        try:
            prompt_with_content: str = prompt.single_doc_find_keyword_prompt(
                f"'''{docx_2_markdown}'''", more_keywords
            )
            logging.info("提示词合成成功！")
        except Exception as e:
            return {
                'code': 500,
                'info': f"合并Markdown、提示词和关键词库出错: {str(e)}",
                'data': {}
            }

        # 三、第一次传入大模型生成结果
        try:
            logging.info("开始第一次大模型生成...")
            model_1st_output: str = gen_chain(prompt_with_content)
            model_1st_output = model_1st_output.replace(r"\~", "~")
            # 提取JSON内容
            model_1st_output_clean: str = clean_json_string(model_1st_output)
            logging.info("第一次大模型生成完成。")
        except Exception as e:
            return {
                'code': 500,
                'info': f"第一次大模型处理出错: {str(e)}",
                'data': {}
            }

        # 四、处理大模型第一次的输出
        if not model_1st_output_clean:
            return {
                'code': 500,
                'info': "第一次大模型处理无输出。",
                'data': {}
            }
        elif model_1st_output_clean == "null":
            return {
                'code': 200,
                'info': f"未找到关键词和值。\n文件ID: {file_id}\n模型输出: {model_1st_output}",
                'data': 'null'
            }
        else:
            try:
                # 解析JSON
                model_1st_output_clean_dict: Dict[str, Any] = json.loads(model_1st_output_clean)
                for info in model_1st_output_clean_dict.get("result", []):
                    msg = "|".join(str(v) for v in info.values())
                    pub_gen(task_id, msg, file_info.get("docId"), file_info.get("taskDocId"))
                # 扩展JSON键名
                model_1st_output_clean_dict = model_1st_output_expand_keys(model_1st_output_clean_dict)
            except json.JSONDecodeError as e:
                return {
                    'code': 500,
                    'info': f"解析大模型输出JSON出错: {str(e)}",
                    'data': {}
                }
            except Exception as e:
                return {
                    'code': 500,
                    'info': f"处理大模型输出出错: {str(e)}",
                    'data': {}
                }

    # 五、获取文档的JSON格式内容
    try:
        json_content: Dict[str, Any] = dmsoperator.get_jsonfile(file_id)
        logging.info("获取文档JSON内容成功。")
    except Exception as e:
        return {
            'code': 500,
            'info': f"获取文档JSON内容出错: {str(e)}",
            'data': {}
        }

    # 六、处理关键词和值的位置信息
    try:
        kv_pair_list: List[Dict[str, Any]] = process_keywords_and_values(
            json_content, model_1st_output_clean_dict
        )
        kv_pair_dict: Dict[str, Any] = {"kv_json": kv_pair_list}
        logging.info("处理关键词和值的位置信息成功。")
    except Exception as e:
        return {
            'code': 500,
            'info': f"处理关键词和值出错: {str(e)}",
            'data': {}
        }

    # （性能优化）如果是单文档处理，进入以下流程
    if single_file_flag:
        # 七、基于不同关键词和不同值的分组信息
        try:
            # 提取大模型第一次输出的结果部分
            model_1st_output_clean_dict_result: List[Dict[str, Any]] = model_1st_output_clean_dict['result']
            model_1st_output_clean_dict_result_str: str = json.dumps(
                model_1st_output_clean_dict_result, indent=2, ensure_ascii=False
            )
            # 合并JSON和提示词
            prompt_with_json: str = prompt.find_same_mean_keyword_prompt(model_1st_output_clean_dict_result_str)
            # 第二次传入大模型生成结果
            model_2nd_output: str = gen_chain(prompt_with_json)
            # 处理大模型第二次的输出
            model_2nd_output_clean: str = clean_json_string(model_2nd_output)
            model_2nd_output_clean_dict: Dict[str, Any] = json.loads(model_2nd_output_clean)
            # 扩展JSON键名
            model_2nd_output_clean_dict = model_2nd_output_expand_keys(model_2nd_output_clean_dict)
            # 进行分组
            different_kvs_based_group_dict: Dict[str, Any] = grouping_based_on_different_kvs(
                model_1st_output_clean_dict, model_2nd_output_clean_dict
            )
            logging.info("分组信息生成成功。")
        except json.JSONDecodeError as e:
            return {
                'code': 500,
                'info': f"解析第二次大模型输出JSON出错: {str(e)}",
                'data': {}
            }
        except Exception as e:
            return {
                'code': 500,
                'info': f"分组处理出错: {str(e)}",
                'data': {}
            }

        # 八、准备最终输出
        try:
            model_1st_output_clean_str: str = json.dumps(
                model_1st_output_clean_dict, indent=2, ensure_ascii=False
            )
            kv_pair_with_location_str: str = json.dumps(
                kv_pair_dict, indent=2, ensure_ascii=False
            )
            different_kvs_based_group_str: str = json.dumps(
                different_kvs_based_group_dict, indent=2, ensure_ascii=False
            )
            logging.info("最终输出准备成功。")
        except Exception as e:
            return {
                'code': 500,
                'info': f"单文档转换输出为字符串出错: {str(e)}",
                'data': {}
            }

        return {
            'code': 200,
            'info': "success",
            'data': [
                model_1st_output_clean_str,
                kv_pair_with_location_str,
                different_kvs_based_group_str
            ]
        }
    
    # （性能优化）如果是多文档处理，进入以下流程
    else:
        # 八、准备最终输出
        try:
            model_1st_output_clean_str: str = json.dumps(
                model_1st_output_clean_dict, indent=2, ensure_ascii=False
            )
            kv_pair_with_location_str: str = json.dumps(
                kv_pair_dict, indent=2, ensure_ascii=False
            )
            logging.info("最终输出准备成功。")
        except Exception as e:
            return {
                'code': 500,
                'info': f"多文档转换输出为字符串出错: {str(e)}",
                'data': {}
            }

        return {
            'code': 200,
            'info': "success",
            'data': [
                model_1st_output_clean_str,
                kv_pair_with_location_str
            ]
        }

def model_1st_output_expand_keys(data: Any) -> Any:
    """
    递归地将嵌套数据结构（字典或列表）中的简写键名替换为完整键名。

    参数：
    - data (Any): 输入的数据结构，可以是字典、列表或其他类型。

    返回值：
    - Any: 返回与输入结构相同类型的数据结构，所有的简写键名都被替换为完整键名。

    键名映射：
    - "i" 替换为 "index"
    - "k" 替换为 "keyword"
    - "v" 替换为 "value"
    - "o" 替换为 "object"

    示例：
    ```python
    input_data = {
        "i": 1,
        "k": "产品规格",
        "v": "250ml：12.5g",
        "o": "产品规格"
    }

    output_data = model_1st_output_expand_keys(input_data)

    # 输出结果：
    {
        "index": 1,
        "keyword": "产品规格",
        "value": "250ml：12.5g",
        "object": "产品规格"
    }
    ```
    """
    key_map: Dict[str, str] = {
        "i": "index",
        "k": "keyword",
        "v": "value",
        "o": "object"
    }

    if isinstance(data, dict):
        return {
            key_map.get(k, k): model_1st_output_expand_keys(v)
            for k, v in data.items()
        }
    elif isinstance(data, list):
        return [model_1st_output_expand_keys(item) for item in data]
    else:
        return data

def model_2nd_output_expand_keys(data: Union[Dict, List, Any], key_map: Dict[str, str] = None) -> Union[Dict, List, Any]:
    """
    递归地将嵌套数据结构（字典或列表）中的简写键名替换为完整键名。

    参数：
    - data (Any): 输入的数据结构，可以是字典、列表或其他类型。
    - key_map (Dict[str, str]): 键名映射表，默认包含 {"i": "indices"}。

    返回值：
    - Any: 返回与输入结构相同类型的数据结构，所有的简写键名都被替换为完整键名。

    示例：
    >>> input_data = {"i": [{"i": 1}, 2]}
    >>> model_2nd_output_expand_keys(input_data)
    {'indices': [{'indices': 1}, 2]}
    """
    if data is None:
        return None

    key_map = key_map or {"i": "indices"}

    if isinstance(data, dict):
        return {key_map.get(k, k): model_2nd_output_expand_keys(v, key_map) for k, v in data.items()}
    elif isinstance(data, list):
        return [model_2nd_output_expand_keys(item, key_map) for item in data]
    else:
        return data

def model_multi_2nd_output_expand_keys(data: Union[Dict, List, Any], key_map: Dict[str, str] = None) -> Union[Dict, List, Any]:
    """
    递归地将嵌套数据结构（字典或列表）中的简写键名替换为完整键名。

    参数：
    - data (Any): 输入的数据结构，可以是字典、列表或其他类型。

    返回值：
    - Any: 返回与输入结构相同类型的数据结构，所有的简写键名都被替换为完整键名。

    键名映射：
    - "sfid" 替换为 "sourse_file_id"
    - "tfid" 替换为 "target_file_id"
    - "s" 替换为 "sourse"
    - "t" 替换为 "target"
    - "i" 替换为 "index"
    - "k" 替换为 "keyword"
    - "v" 替换为 "value"
    - "o" 替换为 "object"
    """
    key_map: Dict[str, str] = {
        "sfid": "sourse_file_id",
        "tfid": "target_file_id",
        "s": "sourse",
        "t": "target",
        "i": "index",
        "k": "keyword",
        "v": "value",
        "o": "object"
    }

    if isinstance(data, dict):
        return {
            key_map.get(k, k): model_multi_2nd_output_expand_keys(v)
            for k, v in data.items()
        }
    elif isinstance(data, list):
        return [model_multi_2nd_output_expand_keys(item) for item in data]
    else:
        return data

##################### optional #####################
def compare_json(obj1, obj2, path=""):
    """
    函数：compare_json
    主要功能：
    该函数递归比较两个类似 JSON 的 Python 对象（`obj1` 和 `obj2`），用于识别结构、类型和值的差异。
    它检查类型不匹配、字典中的缺失键、列表长度不匹配，以及对应路径下的值差异。
    该函数提供关于不匹配的详细输出，包括嵌套结构中的位置（由 `path` 指示）。

    输入参数：
    - obj1, obj2：两个类似 JSON 的对象（字典、列表或基本值），用于进行比较。
    - path（可选）：一个字符串，用于跟踪嵌套结构中当前的位置，便于日志记录。
      默认值为空字符串，递归调用中会更新以指示不匹配的具体位置。

    输出：
    - 函数打印出每个找到的差异的消息：
      - 对应元素的类型不匹配。
      - 某一字典中缺失的键。
      - 某一列表中缺失的索引。
      - 各级别上的值不匹配。
    # 这些输出提供了具体的不匹配位置，有助于追踪两个 JSON 结构之间的差异。

    """
    if type(obj1) != type(obj2):
        print(f"Type mismatch at {path}: {type(obj1).__name__} vs {type(obj2).__name__}")
        return

    if isinstance(obj1, dict):
        keys1 = set(obj1.keys())
        keys2 = set(obj2.keys())

        for key in keys1 - keys2:
            print(f"Key '{path + '.' if path else ''}{key}' missing in second JSON")
        for key in keys2 - keys1:
            print(f"Key '{path + '.' if path else ''}{key}' missing in first JSON")

        for key in keys1 & keys2:
            new_path = f"{path}.{key}" if path else key
            compare_json(obj1[key], obj2[key], new_path)

    elif isinstance(obj1, list):
        len1 = len(obj1)
        len2 = len(obj2)
        min_len = min(len1, len2)

        for i in range(min_len):
            new_path = f"{path}[{i}]"
            compare_json(obj1[i], obj2[i], new_path)

        if len1 > len2:
            for i in range(len2, len1):
                print(f"Index '{path}[{i}]' missing in second JSON")
        elif len2 > len1:
            for i in range(len1, len2):
                print(f"Index '{path}[{i}]' missing in first JSON")

    else:
        if obj1 != obj2:
            print(f"Value mismatch at {path}: {obj1} vs {obj2}")